/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;

/**
 * ExtractClass - An implementation of the Extract Class refactoring technique
 * 
 * @author Isaac
 */
public class ExtractClass extends Refactoring {

    /**
     * Creates a new Instance of ExtractClass
     */
    public ExtractClass()
    {
        this.shortName = "EXCL";
        this.name = "Extract Class";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeGraph )
     */
    @Override
    public void execute(CodeGraph graph)
    {
        ClassNode source = (ClassNode) operands.get(0);
        // 1. Decide how to split the responsibilities of the class
        ClassNode target = splitResponsibilities(source, graph);

        linkClasses(source, target, graph);

        adjustInterfaces(source, target);

        // 9. Decide whether to expose the new class. If you do expose the
        // class, decide whether to expose it as a reference object or as an
        // immutable value object.
    }

    /**
     * Reviews and reduces the interfaces to each class
     * 
     * @param source
     *            source class to review
     * @param target
     *            target class to review
     */
    private void adjustInterfaces(ClassNode source, ClassNode target)
    {
        // 8. Review and reduce the interfaces of each class
    }

    /**
     * links the source class and target class within the provided graph
     * 
     * @param source
     *            class to be linked
     * @param target
     *            class to be linked
     * @param graph
     *            graph in which classes and any generated attributes are placed
     */
    private void linkClasses(ClassNode source, ClassNode target, CodeGraph graph)
    {
        // 3. Make a link from the old to the new class
        AttributeNode aNode = new AttributeNode(source, "a" + target.getClassName(), "");
        aNode.setAccessibility(Accessibility.Private);
        aNode.setType(target.getClassName());
        aNode.setAssociatedClass(target);

        graph.insert(aNode);
        source.addAttribute(aNode);
        graph.insert(new Edge(source, aNode, EdgeType.Attribute));
        if (!graph.isEdge(source, target, EdgeType.Association))
            graph.insert(new Edge(source, target, EdgeType.Association));
    }

    /**
     * divides the reposiblities of the source class into those that must stay
     * and those that belong in their own class
     * 
     * @param source
     *            class under inspection
     * @param graph
     *            graph in which the class belongs
     * @return new ClassNode containing the split off responsibilities
     */
    private ClassNode splitResponsibilities(ClassNode source, CodeGraph graph)
    {
        // 2. Create a new class to express the split-off responsibilities
        ClassNode target = new ClassNode(source.getIdentifier() + "Split", "");
        target.setAccessibility(Accessibility.PackageProtected);
        graph.insert(target);

        List<AttributeNode> moveFields = new ArrayList<AttributeNode>();
        List<MethodNode> moveMethods = new ArrayList<MethodNode>();
        
        List<CodeNode> srcAttrs = source.getAttributes();
        for (CodeNode node : srcAttrs) {
            if (node instanceof AttributeNode) {
                AttributeNode attr = (AttributeNode)node;
                if (attr.getAssociatedMethods().size() <= 1)
                    moveFields.add(attr);
            }
        }
        
        List<CodeNode> srcMethods = source.getMethods();
        for (CodeNode node : srcMethods) {
            if (node instanceof MethodNode) {
                MethodNode method = (MethodNode)node;
                if (method.getAssociatedMethods().size() < 1 && method.getAssocAttrs().size() < 1)
                    moveMethods.add(method);
            }
        }

        // TODO: There is still more work to do here

        // 6. Use Move Method to move methods over from old to new. Start with
        // lower-level methods (called rather than calling) and build to the
        // higher-level.
        for (MethodNode mNode : moveMethods)
        {
            CodeNode operands[] = { mNode, target };
            Refactoring mover = RefactoringFactory.getInstance().createMoveMethod(operands);
            mover.execute(graph);
        }

        // 4. Use Move Field on each field you wish to move.
        for (AttributeNode aNode : moveFields)
        {
            if (!aNode.getParent().equals(target))
            {
                CodeNode operands[] = { aNode, target };
                Refactoring mover = RefactoringFactory.getInstance().createMoveField(operands);
                mover.execute(graph);
            }
        }

        return target;
    }
}
